package core

import (
	"container/heap"
	"fmt"
	jsoniter "github.com/json-iterator/go"
	"github.com/shopspring/decimal"
	"io/ioutil"
	"matcher/pkg/boot/config"
	"matcher/pkg/boot/logger"
	"matcher/pkg/lib/helper"
	"matcher/pkg/lib/storage"
	"sort"
	"sync"
	"time"
)

type ExecType int

const (
	// 全部成交
	All ExecType = iota

	// 部分成交
	Part

	// 撤单
	Cancel
)

// 成交回报
type ExecReport struct {
	//Typ    ExecType
	Price  decimal.Decimal
	Amount decimal.Decimal
	Time   int64
}

type Matcher struct {
	sync.Mutex   `json:"-"`
	S            *OrderList
	B            *OrderList
	CancelOrders map[uint64]bool
	Symbol       string
	Index        int
}

func NewMatcher(symbol string, index int, jsonData string) *Matcher {
	var obj Matcher
	if jsonData != "" {
		logger.CatchFatal(jsoniter.UnmarshalFromString(jsonData, &obj))
	} else {
		obj = Matcher{
			Mutex:        sync.Mutex{},
			S:            NewOrderList(SellOrder),
			B:            NewOrderList(BuyOrder),
			CancelOrders: make(map[uint64]bool),
			Symbol:       symbol,
			Index:        index,
		}
	}

	storage.Refresh(&obj, 10*time.Second)
	return &obj
}

func (c *Matcher) Buy(buyOrder *Order) {
	c.Lock()
	defer c.Unlock()

	buyOrder.ID, _ = SF.NextID()
	logger.Info("Buy Order: %s", helper.MustMarshalString(buyOrder))

	for {
		var response = &ExecReport{Time: time.Now().Unix()}
		if c.S.Len() == 0 {
			heap.Push(c.B, buyOrder)
			break
		}

		var sellOrder = heap.Pop(c.S).(*Order)
		if _, ok := c.CancelOrders[sellOrder.ID]; ok {
			delete(c.CancelOrders, sellOrder.ID)
			continue
		}

		if buyOrder.Price.LessThan(sellOrder.Price) {
			heap.Push(c.S, sellOrder)
			heap.Push(c.B, buyOrder)
			break
		} else {
			response.Price = buyOrder.Price
			if buyOrder.Amount.GreaterThan(sellOrder.Amount) {
				buyOrder.Amount = buyOrder.Amount.Sub(sellOrder.Amount)
				response.Amount = sellOrder.Amount
			} else {
				sellOrder.Amount = sellOrder.Amount.Sub(buyOrder.Amount)
				response.Amount = buyOrder.Amount
				buyOrder.Amount = decimal.NewFromFloat(0)
				if sellOrder.Amount.GreaterThan(decimal.NewFromFloat(0)) {
					heap.Push(c.S, sellOrder)
				}
			}
		}

		logger.Info("Order Response", helper.MustMarshalString(response))
		if buyOrder.Amount.IsZero() {
			break
		}
	}
}

func (c *Matcher) Sell(sellOrder *Order) {
	c.Lock()
	defer c.Unlock()

	sellOrder.ID, _ = SF.NextID()
	logger.Info("Sell Order: %s", helper.MustMarshalString(sellOrder))

	for {
		var response = &ExecReport{Time: time.Now().Unix()}
		if c.B.Len() == 0 {
			heap.Push(c.S, sellOrder)
			break
		}

		var buyOrder = heap.Pop(c.B).(*Order)
		if _, ok := c.CancelOrders[buyOrder.ID]; ok {
			delete(c.CancelOrders, buyOrder.ID)
			continue
		}

		if buyOrder.Price.LessThan(sellOrder.Price) {
			heap.Push(c.S, sellOrder)
			heap.Push(c.B, buyOrder)
			break
		} else {
			response.Price = sellOrder.Price
			if buyOrder.Amount.GreaterThan(sellOrder.Amount) {
				buyOrder.Amount = buyOrder.Amount.Sub(sellOrder.Amount)
				response.Amount = sellOrder.Amount
				sellOrder.Amount = decimal.NewFromFloat(0)
				if buyOrder.Amount.GreaterThan(decimal.NewFromFloat(0)) {
					heap.Push(c.B, buyOrder)
				}
			} else {
				sellOrder.Amount = sellOrder.Amount.Sub(buyOrder.Amount)
				response.Amount = buyOrder.Amount
			}
		}

		logger.Info("Order Response", helper.MustMarshalString(response))
		if sellOrder.Amount.IsZero() {
			break
		}
	}
}

func (c *Matcher) Refresh() {
	c.Lock()
	var content = helper.MustMarshal(c)
	c.Unlock()

	var path = fmt.Sprintf("%s/runtime/symbols/%s.json", config.ROOT_PATH, c.Symbol)
	logger.CatchFatal(ioutil.WriteFile(path, content, 0777))
}

func (c *Matcher) GetDepth() *Depth {
	c.Lock()
	defer c.Unlock()

	var result = &Depth{
		Asks: make([]DepthItem, 0),
		Bids: make([]DepthItem, 0),
	}

	var buyPricesMapping = make(map[string]decimal.Decimal)
	for _, item := range c.B.Data {
		if _, ok := c.CancelOrders[item.ID]; ok {
			continue
		}

		var p = item.Price.StringFixed(2)
		if record, ok := buyPricesMapping[p]; !ok {
			buyPricesMapping[p] = item.Amount
		} else {
			buyPricesMapping[p] = record.Add(item.Amount)
		}
	}
	var buyPrices = NumericStrings{}
	for k, _ := range buyPricesMapping {
		buyPrices = append(buyPrices, k)
	}
	sort.Sort(buyPrices)
	buyPrices.Reverse()
	var m = len(buyPrices)
	if m > 10 {
		m = 10
	}
	for i := 0; i < m; i++ {
		result.Bids = append(result.Bids, DepthItem{
			Price:  buyPrices[i],
			Amount: buyPricesMapping[buyPrices[i]].StringFixed(2),
		})
	}

	var sellPricesMapping = make(map[string]decimal.Decimal)
	for _, item := range c.S.Data {
		if _, ok := c.CancelOrders[item.ID]; ok {
			continue
		}

		var p = item.Price.StringFixed(2)
		if record, ok := sellPricesMapping[p]; !ok {
			sellPricesMapping[p] = item.Amount
		} else {
			sellPricesMapping[p] = record.Add(item.Amount)
		}
	}
	var sellPrices = NumericStrings{}
	for k, _ := range sellPricesMapping {
		sellPrices = append(sellPrices, k)
	}
	sort.Sort(sellPrices)
	var n = len(sellPrices)
	if n > 10 {
		n = 10
	}
	sellPrices.Reverse()

	for i := 0; i < n; i++ {
		result.Asks = append(result.Asks, DepthItem{
			Price:  sellPrices[i],
			Amount: sellPricesMapping[sellPrices[i]].StringFixed(2),
		})
	}

	return result
}

func (c *Matcher) Cancel(id uint64) {
	c.Lock()
	defer c.Unlock()
	c.CancelOrders[id] = true
}
